This book describes the characteristics of the Oberon/L compiler. Moreover it gives an illustration of how to access the underlying Macintosh Toolbox from Oberon/F to write stand-alone applications or Mac OS dependent Oberon/F components.
The first part would seem to be interesting to all Oberon/F programmers and should contribute to the understanding of the internal mechanisms of the runtime system.
The second part is directed towards those programmers who want to make use of the Macintosh Toolbox directly and therfore sacrifice portability.
In the third part a description of module SYSTEM is given.
Compiler Issues
Typesize, Alignment and Calling Conventions
The size of Oberon basetypes is listed below. Composite types (records, arrays) are constructed out of these basetypes. Record fields and array elements whose sizes are larger than one byte are aligned to the next word (two bytes) boundary.
Type Size
SHORTINT 1 byte
INTEGER 2 bytes
LONGINT 4 bytes
REAL 4 bytes
LONGREAL 8 bytes
CHAR 1 byte
BOOLEAN 1 byte
SET 4 bytes
POINTER 4 bytes
All procedure calls in Oberon/F are stack-based. The parameters are pushed according to the definition in Inside Macintosh (I-90). The space for a function result is allocated on the stack by the compiler before pushing parameters and calling the function. Used registers are saved by the caller and restored after returning from the called procedure. To prevent the stack from growing into the heap the stackpointer is checked upon every entry into a procedure, a trap is generated if the test fails.
Runtime Type System
All dynamically allocated records contain a hidden field, which is called the type tag. The type tag points to the type descriptor that contains all type information needed at runtime. Calls of type-bound procedures, type tests and the garbage collector all use the information which is stored in the type descriptor. The type descriptor is allocated only once for every record type in the system.
Dynamically allocated arrays use a size descriptor to check the bounds of indexes. The size descriptor also contains the type tag of the elementtype. For every single dynamic array a size descriptor is needed.
System Flags
When interfacing to the operating system you must be able to override the default behavior of the compiler. Two different classes of system flags are used to parameterize type and procedure declarations. The extension of the Oberon
2 syntax is given below. You must import the module SYSTEM to use the extended syntax.
UNTAGGED 1 Record: No type tag and no type descriptor are allocated.
No type-bound procedures are allowed.
Pointers to untagged records and extensions of untagged records inherit the attribute of being untagged.
Array: Size descriptor is not allocated.
Only one-dimensional untagged open arrays are allowed.
Bounds are not checked when indexing.
Pointers to untagged arrays inherit the attribute of being untagged.
Pointer: Calling NEW is not allowed, call MacMemoryMgr.NewPtr instead.
Untagged pointers are not traced by the garbage collector.
You must free the heap space manually by calling MacMemoryMgr.DisposePtr.
A (tagged) record may be removed by the garbage collector if referenced by untagged pointers only.
HANDLE 2 Pointer: Allowed only in pointer type. Eliminates the need to define an intermediate pointer type. Automatic double dereference when accessing fields of the associated record or array. Handles are always untagged.
If the value of the intermediate pointer is required use the function procedure SYSTEM.ADR with the first record field as parameter, e.g. ptr := SYSTEM.ADR(myPicHandle.picSize).
CODE 1 Procedure: Definition of a code procedure. When calling a code procedure the byte string is directly inserted into the calling code after the parameters have been pushed. A combination with other system flags is not allowed.
CALLBACK 2 Procedure: Upon entry into a callback-procedure the registers (D0-D7, A0-A4) are saved. Used for procedures which are called by the operating system, for example procedures that are assigned to the parameter actionProc in MacControlMgr.TrackControl.
NOSTKCHK 4 Procedure: Disables the stack overflow check upon procedure entry. Necessary for procedures called at interrupt time, e.g. handler in VBLTask (MacVBLMgr.VBLTask.vblAddr).
The values of the system flags should be defined in the CONST section of the module. The system flags CALLBACK and NOSTKCHK can be combined by adding their values. A typical definition of a VBLTaskHandler is therefore: PROCEDURE [CALLBACK + NOSTKCHK] Handle;
Implementation Restrictions
max. codesize: 8 MB per module.
max. global VAR size: unlimited
max. local VAR size: unlimited
max. CONST size: unlimited
max. imported modules: 127 per module (direct or indirect)
max. exported objects: unlimited (except types)
max. exported types: 1024 per module
max. height of type hierachy: 16 (including basetype)
max. loaded modules: unlimited
Interfacing the Macintosh Toolbox
Interface Modules
The Macintosh Toolbox is accessed through a set of interface modules, together they form the subsystem Mac. Interface modules are common Oberon modules, but importing a interface module makes your code Mac OS dependent. Every interface module provides access to one Macintosh Toolbox Manager. All calls to routines in the interface modules are stack-based, the interface modules provide the glue code wherever it is necessary.
Type extension is used for defining hierarchical types. This makes access to record fields more natural, first because there is no need for a distinction between Ptr and Peek. Peeks are not defined in the interface modules as they would never be used. Second you can access the fields of basetypes directly without having to specify the record first.
An example:
DialogPtr* = POINTER TO DialogRecord;
DialogRecord* = RECORD (MacWindowMgr.WindowRecord)
items*: MacTypes.Handle;
textH*: MacTextEdit.TEHandle;
editField*: INTEGER;
editOpen: INTEGER; (* used internally, therefore not exported *)
aDefItem*: INTEGER
END;
VAR dlg: DialogPtr;
rect := dlg.portRect; (* portRect is a field of GrafPort *)
items := dlg.items; (* in Pascal this would be: DialogPeek(dlg).items *)
MacWindowMgr.HideWindow(dlg); (* dlg is an extension of WindowPtr *)
Common types such as Str255 and Handle are defined in the module MacTypes. The interface module MacTypes also exports procedures to convert zero-terminated strings (Oberon, C) into pascal-type strings with a leading length byte.
Composing New Interface Modules
If you want to add interface modules which are not part of this distribution, you can create them yourself. Keep in mind that all operating system records have to be declared as untagged.
An example for a routine which requires glue code:
PROCEDURE NewPtr* (byteCount: Size): Ptr; (* Routine with glue *)
VAR p: Ptr;
BEGIN
SYSTEM.PUTREG(D0, byteCount);
newPtr;
SYSTEM.GETREG(D0, err);
SYSTEM.GETREG(A0, p);
RETURN p
END NewPtr;
Writing Macintosh Applications Using Oberon/F
After a short introduction to Macintosh programming in Oberon/F through several examples we describe the command interface of the Linker and give an overview what to bear in mind when using garbage collection and NEW in your stand-alone applications.
The first example displays the string "Hello World" in a window and waits until the mouse button is pressed. To reduce the typing effort and the readability of the program we used aliasing when importing the interface modules. Notice that the QuickDraw globals used during initialization are common global variables of the interface module MacQuickDraw. To compile and link the application execute the two commands in the module header. The applicationfile is generated in the Samples folder.
The following example uses the File Manager and QuickDraw to a greater extent. When run, the application opens a PICT-file and displays the picture in a window which occupies the main screen. Use Compile
A common pitfall when porting Pascal programs is that the CASE-statement in Oberon/L generates a runtime error if none of the case labels holds and no ELSE-branch is available. As a consequence you must add an empty ELSE-branch to your message handlers.
The Linker
Stand-alone applications are linked using DevLinker. DevLinker copies the relevant information out of the code files into resources of type "oOBF". Additional boot information is stored in resources of type "bOBF". A "CODE"-resource containing the boot loader is also generated. Resources with other types are left untouched, you don't need to specify a seperate resource file from which to copy all resources.
application: Filename of the application. A new file is generated if no file with the specified name exists otherwise the old file is reused. Existing resources of the types "CODE", "oOBF" and "bOBF" are removed before linking takes place.
module0..i: Modules linked into the application. Imported modules are linked automatically, you need to list only the top modules. For instance, all necessary interface modules are linked.
mainmodule: At startup the bodies of the main module and all underlying modules are executed. Usually the toplevel module is specified, which should also appear as the last in the list of linked modules. Modules on the same level are initialized in the order in which they appear in the link list or import list of the importing module. The mainmodule should always be the last in the modulelist with one exception: the Kernel is always first in the module list (see next chapter).
Using NEW and Garbage Collection in Your Applications
If you are calling NEW in your application and thereby implicitly use the garbage collector, you must link the Kernel into the application. The NEW-procedure is implemented in the kernel, the compiler just generates the code to call this procedure. So every module using NEW has a hidden import of the kernel. The kernel must be the first module in the list of linked modules and should be specified as the main module. During initialization, the kernel seizes control from the boot loader and traverses the list of loaded modules which has been generated by the boot loader and calls subsequently all module bodies. Don't call MacOSUtils.ExitToShell directly when "importing" the kernel, call Kernel.Quit with parameter zero instead to assure that occupied system resources get properly released before the application is terminated.
Programs don't need to call the garbage collector explicitly. If the NEW-procedure cannot satisfy a request for heap space it calls the garbage collector internally before allocating a new heap block from the Macintosh Memory Manager. The garbage collector marks pointers in stackframes and is able to run anytime except at interrupt-time. As a consequence calling NEW is not allowed during interrupt handling.
Accessing the Macintosh Toolbox when Programming Components in Oberon/F
As an example of a view which depends on the underlying operating system we provide stripped-down source code of the module HostPictures:
Module SYSTEM contains certain procedures that are necessary to implement low-level operations. It is strongly recommended to restrict its use to specific low-level modules, as such modules are inherently non-portable. SYSTEM is not considered as part of Oberon/L proper.
The procedures contained in module SYSTEM are listed in the following table. v stands for a variable. x, y, and n stands for expressions. T stands for a type. P stands for a procedure. M[a] stands for memory value at address a.
Function procedures
Name Argument types Result type Description
ADR(v) any LONGINT address of variable v
ADR(P) P:PROCEDURE LONGINT address of Procedure P
ADR(T) T: a record type LONGINT address of Descriptor of T
LSH(x, n) x, n: integer type type of x logical shift (n > 0: left, n < 0: right)
ROT(x, n) x, n: integer type type of x rotation (n > 0: left, n < 0: right)
VAL(T, x) T, x: any type T x interpreted as of type T
Proper procedures
Name Argument types Description
GET(a, v) a: LONGINT; v: any basic type, v := M[a]
pointer type, procedure type
PUT(a, x) a: LONGINT; x: any basic type, M[a] := x
pointer type, procedure type
GETREG(n, v) n: integer constant, v: any basic type, v := Register n
pointer type, procedure type
PUTREG(n, x) n: integer constant, x: any basic type, Register n := x